package com.ruihua.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruihua.pojo.RedisData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Slf4j
@Component
public class CacheClient {

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
    }
    //逻辑过期
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){
        //设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        //设置过期多少秒过期，使用工具转为秒
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        //写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));

    }

    //缓存穿透
    //要告诉我 是什么类型
    //函数编程Function 有参有反
    public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time, TimeUnit unit){
        String key = keyPrefix + id;
        //先查redis
        String json = stringRedisTemplate.opsForValue().get(key);
        //判断存在 只有"abc"才通过 转对象返回
        if (StrUtil.isNotBlank(json)){
            System.out.println("工具包里的" + 1);//为什么会走这一行
            return JSONUtil.toBean(json, type);
        }
        //判断命中是否为空值 为""，因为""也是不为null
        if (json != null){
            //返回错误
            return null;
        }
        //去数据库查
        R r =dbFallback.apply(id);
        //如果数据库没有，保存redis2分钟
        if (r == null){
//            redisUtils.set(key,"",120L);
            stringRedisTemplate.opsForValue().set(key, "", RedisConstants.Index_LEADERSHIP_TTL, TimeUnit.MINUTES);
            return null;
        }
        this.set(key,r,time,unit);
        return r;
    }

    //创建线程池，创建10个
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    //逻辑过期
    /**
     *
     * @param keyPrefix key
     * @param id 用户id
     * @param type 类的类型
     * @param dbFunction lambda查数据库,mybatis,mybatis-plus
     * @param time 时间L
     * @param unit 时间单位
     * @param <R> 任意
     * @param <ID> 用户id
     * @return
     */
    public <R,ID> R queryWithLogicalExpire(
            String keyPrefix,ID id,Class<R> type,Function<ID,R> dbFunction,Long time,TimeUnit unit){
        String key = keyPrefix + id;
        //1.从redis查询用户信息
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.如果为空 返回
        if (StrUtil.isBlank(json)){
            //3.为空，直接返回
            return null;
        }
        //4.命中先把josn反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        JSONObject data = (JSONObject)redisData.getData();
        R r = JSONUtil.toBean(data, type);

        LocalDateTime expireTime = redisData.getExpireTime();
        System.out.println(expireTime);
        //5.判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())){//判断过期时间是不是在当前时间之后
            //5.1.未过期，直接返回用户信息
            return r;
        }

        //5.2.已过期，需要缓存重建
        //6.缓存重建
        //6.1.获取互斥锁
        String lockKey = RedisConstants.Lock_LEADERSHIP_KEY +id;
        boolean isLock = tryLock(lockKey);//拿到锁的结果
        //6.2.判断是否获取锁成功
        if (isLock){
            //6.3.成功，开启独立线程，实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    //先查数据库
                    R r1 = dbFunction.apply(id);
                    //写入Redis
                    this.setWithLogicalExpire(key,r1,time,unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    //释放锁
                    unlock(lockKey);
                }
            });
        }
        //6.4.返回过期的用户信息
        return r;
    }


    //尝试获取锁
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//10秒
        //不能直接返回，因为可能会拆箱
        return BooleanUtil.isTrue(flag);
    }
    //释放锁
    private void unlock(String key){
        stringRedisTemplate.delete(key);
    }

}
